timsort: Add gtk_tim_sort_set_runs()
authorBenjamin Otte <otte@redhat.com>
Sat, 11 Jul 2020 04:02:58 +0000 (06:02 +0200)
committerBenjamin Otte <otte@redhat.com>
Wed, 22 Jul 2020 12:04:40 +0000 (14:04 +0200)
... and use it in the SortListModel

Setting runs allows declaring already sorted regions so the sort does
not attempt to sort them again.

This massively speeds up partial inserts where we can reuse the sorted
model as a run and only resort the newly inserted parts.

Benchmarks:

    appending half the model
                    qsort  timsort
    128,000 items    94ms     69ms
    256,000 items   202ms    143ms
    512,000 items   488ms    328ms

    appending 1 item
                    qsort  timsort
      8,000 items   1.5ms    0.0ms
     16,000 items   3.1ms    0.0ms
              ...
    512,000 items     ---    1.8ms

gtk/gtksortlistmodel.c
gtk/gtktimsort-impl.c
gtk/gtktimsort.c
gtk/gtktimsortprivate.h

index cdfce89f01f702f4faf05ae92324c0d528646dee..f7db707e5ad10bdf6173ed6e0e9709e035b627ca 100644 (file)
@@ -175,13 +175,22 @@ sort_func (gconstpointer a,
 }
 
 static void
-gtk_sort_list_model_resort (GtkSortListModel *self)
+gtk_sort_list_model_resort (GtkSortListModel *self,
+                            guint             already_sorted)
 {
-  gtk_tim_sort (sort_array_get_data (&self->items),
-                sort_array_get_size (&self->items),
-                sizeof (SortItem),
-                sort_func,
-                self->sorter);
+  GtkTimSort sort;
+
+  gtk_tim_sort_init (&sort,
+                     sort_array_get_data (&self->items),
+                     sort_array_get_size (&self->items),
+                     sizeof (SortItem),
+                     sort_func,
+                     self->sorter);
+  gtk_tim_sort_set_runs (&sort, (gsize[2]) { already_sorted, 0 });
+
+  while (gtk_tim_sort_step (&sort));
+
+  gtk_tim_sort_finish (&sort);
 }
 
 static void
@@ -252,7 +261,7 @@ gtk_sort_list_model_items_changed_cb (GListModel       *model,
         {
           sort_array_append (&self->items, &(SortItem) { g_list_model_get_item (self->model, i), i });
         }
-      gtk_sort_list_model_resort (self);
+      gtk_sort_list_model_resort (self, sort_array_get_size (&self->items) - added);
 
       for (i = 0; i < start; i++)
         {
@@ -339,7 +348,7 @@ gtk_sort_list_model_sorter_changed_cb (GtkSorter        *sorter,
   else if (sort_array_is_empty (&self->items))
     gtk_sort_list_model_create_items (self);
 
-  gtk_sort_list_model_resort (self);
+  gtk_sort_list_model_resort (self, 0);
 
   n_items = g_list_model_get_n_items (G_LIST_MODEL (self));
   if (n_items > 1)
@@ -476,7 +485,7 @@ gtk_sort_list_model_set_model (GtkSortListModel *self,
       added = g_list_model_get_n_items (model);
 
       gtk_sort_list_model_create_items (self);
-      gtk_sort_list_model_resort (self);
+      gtk_sort_list_model_resort (self, 0);
     }
   else
     added = 0;
index e6f9f3bba601c91564bd6a867d4de25d17f439c1..5dd1c98d3f4cdea6f9f454e6e68f00d355f79091 100644 (file)
@@ -215,10 +215,6 @@ gtk_tim_sort(merge_append) (GtkTimSort *self)
   /* Push run onto pending-run stack, and maybe merge */
   gtk_tim_sort_push_run (self, self->base, run_len);
 
-  /* Advance to find next run */
-  self->base = ELEM (self->base, run_len);
-  self->size -= run_len;
-
   return TRUE;
 }
 
index 451a63a9021cc5c7ec9796268b24d0b1e26e9bae..c59682cebddc39c5d9ceb5ac39fab512739fdc16 100644 (file)
@@ -125,10 +125,15 @@ gtk_tim_sort_push_run (GtkTimSort *self,
                        gsize       len)
 {
   g_assert (self->pending_runs < GTK_TIM_SORT_MAX_PENDING);
+  g_assert (len <= self->size);
 
   self->run[self->pending_runs].base = base;
   self->run[self->pending_runs].len = len;
   self->pending_runs++;
+
+  /* Advance to find next run */
+  self->base = ((char *) self->base) + len * self->element_size;
+  self->size -= len;
 }
 
 /**
@@ -167,6 +172,54 @@ gtk_tim_sort_ensure_capacity (GtkTimSort *self,
   return self->tmp;
 }
 
+/*<private>
+ * gtk_tim_sort_get_runs:
+ * @self: a #GtkTimSort
+ * @runs: (out) (caller-allocates): Place to store the 0-terminated list of
+ *     runs
+ *
+ * Stores the already presorted list of runs - ranges of items that are
+ * known to be sorted among themselves.
+ *
+ * This can be used with gtk_tim_sort_set_runs() when resuming a sort later.
+ **/
+void
+gtk_tim_sort_get_runs (GtkTimSort *self,
+                       gsize       runs[GTK_TIM_SORT_MAX_PENDING + 1])
+{
+  gsize i;
+
+  g_return_if_fail (self);
+  g_return_if_fail (runs);
+
+  for (i = 0; i < self->pending_runs; i++)
+    runs[i] = self->run[i].len;
+}
+
+/*<private>
+ * gtk_tim_sort_set_runs:
+ * @self: a freshly initialized #GtkTimSort
+ * @runs: (array length=zero-terminated): a 0-terminated list of runs
+ *
+ * Sets the list of runs. A run is a range of items that are already
+ * sorted correctly among themselves. Runs must appear at the beginning of
+ * the array.
+ *
+ * Runs can only be set at the beginning of the sort operation.
+ **/
+void
+gtk_tim_sort_set_runs (GtkTimSort *self,
+                       gsize      *runs)
+{
+  gsize i;
+
+  g_return_if_fail (self);
+  g_return_if_fail (self->pending_runs == 0);
+
+  for (i = 0; runs[i] != 0; i++)
+    gtk_tim_sort_push_run (self, self->base, runs[i]);
+}
+
 #if 1
 #define WIDTH 4
 #include "gtktimsort-impl.c"
index d44c74c470b762db5fcb9a8efe7da08e5b705ab1..f6ebd348e9a55ee50faa5c70006919dccd90b591 100644 (file)
@@ -95,6 +95,11 @@ void            gtk_tim_sort_init                               (GtkTimSort
                                                                  gpointer                data);
 void            gtk_tim_sort_finish                             (GtkTimSort             *self);
 
+void            gtk_tim_sort_get_runs                           (GtkTimSort             *self,
+                                                                 gsize                   runs[GTK_TIM_SORT_MAX_PENDING + 1]);
+void            gtk_tim_sort_set_runs                           (GtkTimSort             *self,
+                                                                 gsize                  *runs);
+
 gboolean        gtk_tim_sort_step                               (GtkTimSort             *self);
 
 void            gtk_tim_sort                                    (gpointer                base,